// nettest.cc 
//	Test out message delivery between two "Nachos" machines,
//	using the Post Office to coordinate delivery.
//
//	Two caveats:
//	  1. Two copies of Nachos must be running, with machine ID's 0 and 1:
//		./nachos -m 0 -o 1 &
//		./nachos -m 1 -o 0 &
//
//	  2. You need an implementation of condition variables,
//	     which is *not* provided as part of the baseline threads 
//	     implementation.  The Post Office won't work without
//	     a correct implementation of condition variables.
//
// Copyright (c) 1992-1993 The Regents of the University of California.
// All rights reserved.  See copyright.h for copyright notice and limitation 
// of liability and disclaimer of warranty provisions.

#include "copyright.h"

#include "system.h"
#include "network.h"
#include "post.h"
#include "interrupt.h"
#include "bitmap.h"

#define MAX_LOCK 200
#define MAX_CV 200
#define MAX_MV 200
#define MAX_THREAD 50
#define MAX_MAILBOX 10
#define MAX_CLIENT 500

// Test out message delivery, by doing the following:
//	1. send a message to the machine with ID "farAddr", at mail box #0
//	2. wait for the other machine's message to arrive (in our mailbox #0)
//	3. send an acknowledgment for the other machine's message
//	4. wait for an acknowledgement from the other machine to our 
//	    original message

void
MailTest(int farAddr)
{
    PacketHeader outPktHdr, inPktHdr;
    MailHeader outMailHdr, inMailHdr;
    char *data = "Hello there!";
    char *ack = "Got it!";
    char buffer[MaxMailSize];

    // construct packet, mail header for original message
    // To: destination machine, mailbox 0
    // From: our machine, reply to: mailbox 1
    outPktHdr.to = farAddr;		
    outMailHdr.to = 0;
    outMailHdr.from = 1;
    outMailHdr.length = strlen(data) + 1;

    // Send the first message
    bool success = postOffice->Send(outPktHdr, outMailHdr, data); 

    if ( !success ) {
      printf("The postOffice Send failed. You must not have the other Nachos running. Terminating Nachos.\n");
      interrupt->Halt();
    }

    // Wait for the first message from the other machine
    postOffice->Receive(0, &inPktHdr, &inMailHdr, buffer);
    printf("Got \"%s\" from %d, box %d\n",buffer,inPktHdr.from,inMailHdr.from);
    fflush(stdout);

    // Send acknowledgement to the other machine (using "reply to" mailbox
    // in the message that just arrived
    outPktHdr.to = inPktHdr.from;
    outMailHdr.to = inMailHdr.from;
    outMailHdr.length = strlen(ack) + 1;
    success = postOffice->Send(outPktHdr, outMailHdr, ack); 

    if ( !success ) {
      printf("The postOffice Send failed. You must not have the other Nachos running. Terminating Nachos.\n");
      interrupt->Halt();
    }

    // Wait for the ack from the other machine to the first message we sent.
    postOffice->Receive(1, &inPktHdr, &inMailHdr, buffer);
    printf("Got \"%s\" from %d, box %d\n",buffer,inPktHdr.from,inMailHdr.from);
    fflush(stdout);

    // Then we're done!
    interrupt->Halt();
}

/*

struct ServerLock {
	char name[50];
	BitMap *regClient;
	List *queue;
	int holder;
} locks[MAX_LOCK];

int lockCount = 0;

struct ServerCV {
	char name[50];
	BitMap *regClient;
	List *queue;
} cvs[MAX_CV];

struct CVQueueElement {
	int id;
	int lock;	
};

int cvCount = 0;

struct ServerMV {
	char name[50];
	BitMap *regClient;
	int *value;
	int size;
} mvs[MAX_MV];
	
int mvCount = 0;	

void ccopy(char *des, char* src, int size) {
	for(int i = 0; i < size; i++, des++, src++) {
		*des = *src;
	}
}

int itoa(int x, char *m) {
	char buffer[MaxMailSize];
	int count = 0;
	do {
		buffer[count++] = x % 10;
		x /= 10;
	} while(x);
	for(int i = 0; i < count; i++) {
		m[i] = buffer[count-1-i]+'0';
		DEBUG('N', "%d %c\n",i, m[i]);
	}
	return count;
}

int atoi(char *m, int size) {
	int ret = 0;
	for(int i = 0; i < size; i++, m++) {
		DEBUG('N', "%d %d\n",i, m[i]);
		
		ret = ret * 10 + (*m) - '0';
	}
	return ret;
}

int CreateLock(char* name, int mid, int mb) {
	int id = mid * MAX_MAILBOX + mb;
	for(int i = 0; i < lockCount; i++) {
		if (!strcmp(name, locks[i].name)) {
			// lock exists
			locks[i].regClient->Mark(id);
			return i;
		}
	}
	// create new lock
	strcpy(locks[lockCount].name, name);
	locks[lockCount].regClient = new BitMap(MAX_CLIENT);
	locks[lockCount].holder = 0;
	locks[lockCount].queue = new List;
	locks[lockCount].regClient->Mark(id);
	return lockCount++;
}

void Acquire(int lockid, int mid, int mb) {
	DEBUG('N', "Acquire. %d\n", lockid);
	int id = mid * MAX_MAILBOX + mb;
	// check lock is belong to this machine's thread.
	PacketHeader outPktHdr;
    MailHeader outMailHdr;
	char message[2];
	
	if (lockid < 0 || lockid >= lockCount || !locks[lockid].regClient->Test(id)) {
		// lock is not valid
		DEBUG('N', "Not valid. %d\n", lockid);
		message[0] = 1;
		outPktHdr.to = mid;
		outMailHdr.to = mb;
		outMailHdr.length = 1;
		postOffice->Send(outPktHdr, outMailHdr, message);
	} else if (locks[lockid].holder <= 0 || locks[lockid].holder == id) {
		// lock is available
		DEBUG('N', "avaliable. %d\n", lockid);
		locks[lockid].holder = id;
		message[0] = 0;
		outPktHdr.to = mid;
		outMailHdr.to = mb;
		outMailHdr.length = 1;
		postOffice->Send(outPktHdr, outMailHdr, message);
	} else {
		// lock is held by other machine
		// put id in the queue
		// do not send anything
		locks[lockid].queue->Append((void*)(id+MAGIC_NUMBER));
	}
}

void Release(int lockid, int mid, int mb) {
	DEBUG('N', "Release.\n");
	int id = mid * MAX_MAILBOX + mb;
	// check lock is belong to this machine's thread.
	PacketHeader outPktHdr;
    MailHeader outMailHdr;
	char message[2];
	
	if (lockid < 0 || lockid >= lockCount || !locks[lockid].regClient->Test(id)) {
		// lock is not valid
		message[0] = 1;
		outPktHdr.to = mid;
		outMailHdr.to = mb;
		outMailHdr.length = 1;
		postOffice->Send(outPktHdr, outMailHdr, message);
	} else if (locks[lockid].holder != id) {
		// lock is not belong to the process
		// just confirm back
		message[0] = 0;
		outPktHdr.to = mid;
		outMailHdr.to = mb;
		outMailHdr.length = 1;
		postOffice->Send(outPktHdr, outMailHdr, message);
	} else {
		// lock is held by this process
		// wake up a machine in the queue
		int v = (int)locks[lockid].queue->Remove() - MAGIC_NUMBER;
		locks[lockid].holder = v;
		DEBUG('N', "Release %d %d\n", lockid, v);
		if (v > 0) {
			// other thread acquires the lock
			message[0] = 0;
			outPktHdr.to = v / MAX_MAILBOX;
			outMailHdr.to = v % MAX_MAILBOX;
			outMailHdr.length = 1;
			postOffice->Send(outPktHdr, outMailHdr, message);
		}
		// send release back
		message[0] = 0;
		outPktHdr.to = mid;
		outMailHdr.to = mb;
		outMailHdr.length = 1;
		postOffice->Send(outPktHdr, outMailHdr, message);
	}
}

void DestoryLock(int lockid, int mid, int mb) {
	int id = mid * MAX_MAILBOX + mb;
	// check lock is belong to this machine's thread.
	PacketHeader outPktHdr;
    MailHeader outMailHdr;
	DEBUG('l', "Destory Lock %d %d.\n",lockid, id);
	char message[2];
	
	if (lockid < 0 || lockid >= lockCount || !locks[lockid].regClient->Test(id)) {
		// lock is not valid
		DEBUG('l', "Not Valid.\n");
		
		message[0] = 1;
		outPktHdr.to = mid;
		outMailHdr.to = mb;
		outMailHdr.length = 1;
		postOffice->Send(outPktHdr, outMailHdr, message);
	} else if (locks[lockid].holder != id) {
		// lock is not belong to the process
		// just confirm back
		DEBUG('l', "Destory.\n");
		
		message[0] = 0;
		message[1] = 0;
		outPktHdr.to = mid;
		outMailHdr.to = mb;
		outMailHdr.length = 2;
		locks[lockid].regClient->Clear(id);
		DEBUG('l', "Message %d.\n", message[0]);
		postOffice->Send(outPktHdr, outMailHdr, message);
	} else {
		// lock is held by this process
		// wake up a machine in the queue
		int v = (int)locks[lockid].queue->Remove() - MAGIC_NUMBER;
		locks[lockid].holder = v;
		DEBUG('N', "Release %d %d\n", lockid, v);
		if (v > 0) {
			// other thread acquires the lock
			message[0] = 0;
			outPktHdr.to = v / MAX_MAILBOX;
			outMailHdr.to = v % MAX_MAILBOX;
			outMailHdr.length = 1;
			postOffice->Send(outPktHdr, outMailHdr, message);
		}
		// send release back
		message[0] = 0;
		outPktHdr.to = mid;
		outMailHdr.to = mb;
		outMailHdr.length = 1;
		locks[lockid].regClient->Clear(id);
		postOffice->Send(outPktHdr, outMailHdr, message);
	}
}

int CreateCondition(char* name, int mid, int mb) {
	int id = mid * MAX_MAILBOX + mb;
	for(int i = 0; i < cvCount; i++) {
		if (!strcmp(name, cvs[i].name)) {
			// lock exists
			cvs[i].regClient->Mark(id);
			return i;
		}
	}
	strcpy(cvs[cvCount].name, name);
	cvs[cvCount].regClient = new BitMap(MAX_CLIENT);
	cvs[cvCount].queue = new List;
	cvs[cvCount].regClient->Mark(id);
	return cvCount++;
}

void DestoryCV(int cvid, int mid, int mb) {
	int id = mid * MAX_MAILBOX + mb;
	// check mv is belong to this machine's thread.
	PacketHeader outPktHdr;
    MailHeader outMailHdr;
	char message[2];
	
	if (cvid < 0 || cvid >= cvCount || !cvs[cvid].regClient->Test(id)) {
		// mv is not valid
		message[0] = 1;
		outPktHdr.to = mid;
		outMailHdr.to = mb;
		outMailHdr.length = 1;
		postOffice->Send(outPktHdr, outMailHdr, message);
	} else {
		cvs[cvid].regClient->Clear(id);
		message[0] = 0;
		outPktHdr.to = mid;
		outMailHdr.to = mb;
		outMailHdr.length = 1;
		postOffice->Send(outPktHdr, outMailHdr, message);
	}	
}

void Wait(int cvid, int lockid, int mid, int mb) {
	int id = mid * MAX_MAILBOX + mb;
	// check mv is belong to this machine's thread.
	PacketHeader outPktHdr;
    MailHeader outMailHdr;
	char message[2];
	
	if (cvid < 0 || lockid < 0 || cvid >= cvCount || !cvs[cvid].regClient->Test(id) || 
		lockid >= lockCount || !locks[lockid].regClient->Test(id) ||
		locks[lockid].holder != id) {
		// cv or lock is not valid
		message[0] = 1;
		outPktHdr.to = mid;
		outMailHdr.to = mb;
		outMailHdr.length = 1;
		postOffice->Send(outPktHdr, outMailHdr, message);
	} else {
		// release the lock
		int v = (int)locks[lockid].queue->Remove() - MAGIC_NUMBER;
		locks[lockid].holder = v;
		DEBUG('N', "Release %d %d\n", lockid, v);
		if (v > 0) {
			// other thread acquires the lock
			message[0] = 0;
			outPktHdr.to = v / MAX_MAILBOX;
			outMailHdr.to = v % MAX_MAILBOX;
			outMailHdr.length = 1;
			postOffice->Send(outPktHdr, outMailHdr, message);
		}
		// put thread in the wait queue
		CVQueueElement *t = new CVQueueElement;
		t->id = id;
		t->lock = lockid;
		cvs[cvid].queue->Append((void*)t);
		// do not send anything now
	}	
}

void Signal(int cvid, int lockid, int mid, int mb) {
	int id = mid * MAX_MAILBOX + mb;
	// check mv is belong to this machine's thread.
	PacketHeader outPktHdr;
    MailHeader outMailHdr;
	char message[2];
	
	if (cvid < 0 || lockid < 0 || cvid >= cvCount || !cvs[cvid].regClient->Test(id) || 
		lockid >= lockCount || !locks[lockid].regClient->Test(id) ||
		locks[lockid].holder != id) {
		// cv or lock is not valid
		message[0] = 1;
		outPktHdr.to = mid;
		outMailHdr.to = mb;
		outMailHdr.length = 1;
		postOffice->Send(outPktHdr, outMailHdr, message);
	} else {
		// wake one up
		CVQueueElement *t = (CVQueueElement *)cvs[cvid].queue->Remove();
		List *l = new List;
		if (t != NULL) {
			// match the lock
			while (t->lock != lockid) {
				l->Append((void*)t);
				t = (CVQueueElement *)cvs[cvid].queue->Remove();
				if (t == NULL) break;
			}
			void *tt = l->Remove();
			while (tt != NULL) {
				cvs[cvid].queue->Prepend(tt);
				tt = l->Remove();
			}
			if (t != NULL) {
				DEBUG('L', "Wake one Up.\n");
				int v = t->id;
				Acquire(lockid, v / MAX_MAILBOX, v % MAX_MAILBOX);
				delete t;
			}
		}
		delete l;
		
		// return message
		message[0] = 0;
		outPktHdr.to = mid;
		outMailHdr.to = mb;
		outMailHdr.length = 1;
		postOffice->Send(outPktHdr, outMailHdr, message);
	}	
}

void Broadcast(int cvid, int lockid, int mid, int mb) {
	int id = mid * MAX_MAILBOX + mb;
	// check mv is belong to this machine's thread.
	PacketHeader outPktHdr;
    MailHeader outMailHdr;
	char message[2];
	
	if (cvid < 0 || lockid < 0 || cvid >= cvCount || !cvs[cvid].regClient->Test(id) || 
		lockid >= lockCount || !locks[lockid].regClient->Test(id) ||
		locks[lockid].holder != id) {
		// cv or lock is not valid
		message[0] = 1;
		outPktHdr.to = mid;
		outMailHdr.to = mb;
		outMailHdr.length = 1;
		postOffice->Send(outPktHdr, outMailHdr, message);
	} else {
		// wake all up
		CVQueueElement *t = (CVQueueElement *)cvs[cvid].queue->Remove();
		List *l = new List;
		while (t != NULL) {
			if (t->lock != lockid) {
				l->Append((void*)t);
			} else {
				int v = t->id;
				Acquire(lockid, v / MAX_MAILBOX, v % MAX_MAILBOX);
				delete t;
			}
			t = (CVQueueElement *)cvs[cvid].queue->Remove();
		}
		void *tt = l->Remove();
		while (tt != NULL) {
			cvs[cvid].queue->Prepend(tt);
			tt = l->Remove();
		}
		delete l;
		
		// return message
		message[0] = 0;
		outPktHdr.to = mid;
		outMailHdr.to = mb;
		outMailHdr.length = 1;
		postOffice->Send(outPktHdr, outMailHdr, message);
	}
}

int CreateMV(char *name, int size, int mid, int mb) {
	int id = mid * MAX_MAILBOX + mb;
	for(int i = 0; i < mvCount; i++) {
		if (!strcmp(name, mvs[i].name)) {
			// MV exists
			if (mvs[i].size == size || mvs[i].regClient->NumClear() == MAX_CLIENT) {
				mvs[i].regClient->Mark(id);
				return i;
			} else {
				return -1;
			}
		}
	}
	// create a new mv
	strcpy(mvs[mvCount].name, name);
	mvs[mvCount].regClient = new BitMap(MAX_CLIENT);
	mvs[mvCount].regClient->Mark(id);
	mvs[mvCount].size = size;
	mvs[mvCount].value = new int[size];
	return mvCount++;
}

void DestoryMV(int mvid, int mid, int mb) {
	int id = mid * MAX_MAILBOX + mb;
	// check mv is belong to this machine's thread.
	PacketHeader outPktHdr;
    MailHeader outMailHdr;
	char message[2];
	
	if (mvid < 0 || mvid >= mvCount || !mvs[mvid].regClient->Test(id)) {
		// mv is not valid
		message[0] = 1;
		outPktHdr.to = mid;
		outMailHdr.to = mb;
		outMailHdr.length = 1;
		postOffice->Send(outPktHdr, outMailHdr, message);
	} else {
		mvs[mvid].regClient->Clear(id);
		message[0] = 0;
		outPktHdr.to = mid;
		outMailHdr.to = mb;
		outMailHdr.length = 1;
		postOffice->Send(outPktHdr, outMailHdr, message);
	}	
}

void ReadMV(int mvid, int index, int mid, int mb) {
	int id = mid * MAX_MAILBOX + mb;
	// check mv is belong to this machine's thread.
	PacketHeader outPktHdr;
    MailHeader outMailHdr;
	char message[100];
	if (mvid < 0 || mvid >= mvCount || !mvs[mvid].regClient->Test(id)) {
		// mv is not valid
		message[0] = 0;
		outPktHdr.to = mid;
		outMailHdr.to = mb;
		outMailHdr.length = 1;
		postOffice->Send(outPktHdr, outMailHdr, message);
	} else {
		// read the mv value
		DEBUG('R', "%d\n",mvs[mvid].value[index]);
		message[0] = itoa(mvs[mvid].value[index], message+1);
		outPktHdr.to = mid;
		outMailHdr.to = mb;
		outMailHdr.length = message[0]+1;
		postOffice->Send(outPktHdr, outMailHdr, message);
	}
}

void SetMV(int mvid, int index, int value, int mid, int mb) {
	int id = mid * MAX_MAILBOX + mb;
	// check mv is belong to this machine's thread.
	PacketHeader outPktHdr;
    MailHeader outMailHdr;
	char message[2];
	if (mvid < 0 || mvid >= mvCount || !mvs[mvid].regClient->Test(id)) {
		// mv is not valid
		message[0] = 1;
		outPktHdr.to = mid;
		outMailHdr.to = mb;
		outMailHdr.length = 1;
		postOffice->Send(outPktHdr, outMailHdr, message);
	} else {
		// set the mv value
		mvs[mvid].value[index] = value;
		message[0] = 0;
		outPktHdr.to = mid;
		outMailHdr.to = mb;
		outMailHdr.length = 1;
		postOffice->Send(outPktHdr, outMailHdr, message);
	}
}

void NachosServer() {
	PacketHeader inPktHdr, outPktHdr;
    MailHeader inMailHdr, outMailHdr;
    char buffer[MaxMailSize];
	char message[MaxMailSize];
	char name[MaxMailSize];
	int lockid;
	int cvid;
	int mvid;
	int size;
	int value;
	
	printf("%s\n", "Nachos Server Start...");
    
	while (true) {
		// wait for a request
		postOffice->Receive(0, &inPktHdr, &inMailHdr, buffer);
		// parse the request
		int id;
		DEBUG('N',"%d\n", (int)(*buffer));
		switch (*buffer) {
		case 11:
			// create lock
			// lock name
			// parse argv
			ccopy(name, buffer+2, *(buffer+1));
			name[(int)*(buffer+1)] = 0;
			id = CreateLock(name, inPktHdr.from, inMailHdr.from);
			// send back
			outPktHdr.to = inPktHdr.from;
			outMailHdr.to = inMailHdr.from;
			if (id == -1) {
				outMailHdr.length = 1;
				message[0] = 0;
			} else {
				outMailHdr.length = itoa(id, message)+1;
				message[outMailHdr.length-1] = 0;
			}
			postOffice->Send(outPktHdr, outMailHdr, message);
			break;
		case 12:
			// delete lock
			DEBUG('l', "Destory");
			lockid = (int)buffer[1]*100+(int)buffer[2];
			DestoryLock(lockid, inPktHdr.from, inMailHdr.from);
			break;
		case 13:
			// acquire lock
			// lock id
			lockid = (int)buffer[1]*100+(int)buffer[2];
			Acquire(lockid, inPktHdr.from, inMailHdr.from);
			break;
		case 14:
			// release lock
			DEBUG('N', "Release.\n");
			lockid = (int)buffer[1]*100+(int)buffer[2];
			Release(lockid, inPktHdr.from, inMailHdr.from);
			break;
		case 15:
			// create condition
			// condition name
			// parse argv
			ccopy(name, buffer+2, *(buffer+1));
			name[(int)*(buffer+1)] = 0;
			id = CreateCondition(name, inPktHdr.from, inMailHdr.from);
			// send back
			outPktHdr.to = inPktHdr.from;
			outMailHdr.to = inMailHdr.from;
			if (id == -1) {
				outMailHdr.length = 1;
				message[0] = 0;
			} else {
				outMailHdr.length = itoa(id, message)+1;
				message[outMailHdr.length-1] = 0;
			}
			postOffice->Send(outPktHdr, outMailHdr, message);
			break;
		case 16:
			// destory condition
			cvid = (int)buffer[1]*100 + (int)buffer[2];
			DestoryCV(cvid, inPktHdr.from, inMailHdr.from);
			break;
		case 17:
			// wait
			cvid = (int)buffer[1]*100 + (int)buffer[2];
			lockid = (int)buffer[3]*100 + (int)buffer[4];
			Wait(cvid, lockid, inPktHdr.from, inMailHdr.from);
			break;
		case 18:
			// signal
			cvid = (int)buffer[1]*100 + (int)buffer[2];
			lockid = (int)buffer[3]*100 + (int)buffer[4];
			Signal(cvid, lockid, inPktHdr.from, inMailHdr.from);
			break;
		case 19:
			// broadcast
			cvid = (int)buffer[1]*100 + (int)buffer[2];
			lockid = (int)buffer[3]*100 + (int)buffer[4];
			Broadcast(cvid, lockid, inPktHdr.from, inMailHdr.from);
			break;
		case 21:
			// create mv
			DEBUG('N', "Create MV.\n");
			size = (int)buffer[1]*100+(int)buffer[2];
			ccopy(name, buffer+4, *(buffer+3));
			name[(int)*(buffer+3)] = 0;
			id = CreateMV(name, size, inPktHdr.from, inMailHdr.from);
			// send back
			outPktHdr.to = inPktHdr.from;
			outMailHdr.to = inMailHdr.from;
			if (id == -1) {
				outMailHdr.length = 1;
				message[0] = 0;
			} else {
				outMailHdr.length = itoa(id, message)+1;
				message[outMailHdr.length-1] = 0;
			}
			DEBUG('N', "%d.\n",id);
			postOffice->Send(outPktHdr, outMailHdr, message);
			break;
		case 22:
			// destory mv
			mvid = (int)buffer[1]*100 + (int)buffer[2];
			DestoryMV(mvid, inPktHdr.from, inMailHdr.from);
			break;
		case 23:
			// read mv
			mvid = (int)buffer[1]*100 + (int)buffer[2];
			size = (int)buffer[3]*100 + (int)buffer[4];
			ReadMV(mvid, size, inPktHdr.from, inMailHdr.from);
			break;
		case 24:
			// set mv
			mvid = (int)buffer[1]*100 + (int)buffer[2];
			size = (int)buffer[3]*100 + (int)buffer[4];
			value = atoi(buffer+6, *(buffer+5));
			DEBUG('N', "%d %d %d.\n",mvid, size, value);
			SetMV(mvid, size, value, inPktHdr.from, inMailHdr.from);
			break;
		}
	}
}
*/
